#!/usr/bin/env perl
# $Id: sntp.pl 7eaaa50bb501 2013/07/17 05:34:21 pgurumur $
# Copyright (c) 2013 Prabhu Gurumurthy <pgurumur@gmail.com>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# File sntp.pl generated on Fri, July 05, 2013 01:01:29 AM
#
# __version__  = "$Revision: 7eaaa50bb501 $"
# __author__   = "$Author: pgurumur $"
# __modified__ = "$Date: 2013-07-17 05:34:21Z $"

use strict;
use IO::Socket;
use Time::HiRes qw(time);
use Getopt::Long;

Getopt::Long::Configure ('default', 'bundling');

my $arg = scalar(@ARGV);
my $port = 123;
my $timeout = 5;
my $ntpDate = 2208988800;
my %opt = ();

sub usage {

   push my @program, split (/\//, $0);
   my $program = $program[$#program];

   print "Usage: $program options\n";
   print "options: \n";
   print "       --server | -s [hostname] (required)\n";
   print "       --client | -c [hostname] (required)\n";
   print "       --timeout | -t [time in seconds]\n";
   print "       --help | -h prints this help\n";
   
   exit 1;
}

sub bin2Frac {

  my @bin = split '', shift;
  my $frac = 0;
  while (@bin) { $frac = ($frac + pop @bin) / 2; }

  return $frac;
}

sub frac2Bin {

   my $frac = shift;
   my $bin = "";
   while (length ($bin) < 32) {
   
      $bin = $bin . int ($frac * 2);
      $frac = $frac * 2 - int ($frac * 2);
   }

   return $bin;
}

sub processNtpData {

   my ($serverMessage, $clientMessage) = @_;

   if ($serverMessage and $clientMessage) {

      push my @serverData, 
        unpack (
           "a C3   n B16 n B16 H8   N B32 N B32   N B32 N B32",
           $serverMessage);
      push my @clientData,
        unpack (
           "a C3   n B16 n B16 H8   N B32 N B32   N B32 N B32",
           $clientMessage);

      my $serverRecvTime = ($serverData[13] += bin2Frac ($serverData[14]));
      my $clientRecvTime = ($clientData[13] += bin2Frac ($clientData[14]));

      my $serverXmitTime = ($serverData[15] += bin2Frac ($serverData[16]));
      my $clientXmitTime = ($clientData[15] += bin2Frac ($clientData[16]));

      $serverRecvTime -= $ntpDate;
      $clientRecvTime -= $ntpDate;

      $serverXmitTime -= $ntpDate;
      $clientXmitTime -= $ntpDate;

      my $serverTime = scalar (gmtime ($serverXmitTime));
      my $serverFracTime = sprintf (
         "%03.f",
         1000 * sprintf ("%.3f", $serverXmitTime - int ($serverXmitTime)));
      my $clientTime = scalar (gmtime ($clientXmitTime));
      my $clientFracTime = sprintf (
         "%03.f",
         1000 * sprintf ("%.3f", $clientXmitTime - int ($clientXmitTime)));

      print "$opt{server} time: $serverTime +$serverFracTime\n";
      print "$opt{client} time: $clientTime +$clientFracTime\n";

      my $offset = sprintf (
         "%+.4f", (
            ($serverRecvTime - $clientRecvTime) +
            ($serverXmitTime - $clientXmitTime)) / 2);

      print "Offset: $offset secs\n";
   }
   else { usage (); }
}


sub createSocket {
   my ($server, $client) = @_;

   if ($server and $client) {
      my $servSock = new IO::Socket::INET (
         Proto => "udp",
         PeerAddr => $server,
         PeerPort => $port,
         Timeout => $timeout
      );

      my $clieSock = new IO::Socket::INET (
         Proto => "udp",
         PeerAddr => $client,
         PeerPort => $port,
         Timeout => $timeout
      );

      die "unable to create socket: $!\n" unless ($server and $client);

      my $localTime = time () + $ntpDate;
      my $tm = $localTime - int ($localTime);

      my $serverNtpMessage = pack (
         "B8 C3 N10 B32", 
         '00011011', 
         (0)x12, 
         int ($localTime),
         frac2Bin ($tm));
      
      my $clientNtpMessage = pack (
         "B8 C3 N10 B32", 
         '00011011', 
         (0)x12, 
         int ($localTime),
         frac2Bin ($tm));

      $servSock->send ($serverNtpMessage) or
         die "cannot send data to $opt{server}: $!\n";
      $clieSock->send ($clientNtpMessage) or
         die "cannot send data to $opt{client}: $!\n";

      my $serverIn = "";
      my $clientIn = "";
      my $sout = "";
      my $cout = "";

      my ($serverOut, $clientOut) = undef;

      vec ($serverIn, fileno ($server), 1) = 1;
      vec ($clientIn, fileno ($client), 1) = 1;

      select ($serverOut = $serverIn, undef, $sout = $serverIn, $timeout) or
         die "no answer from server: $opt{server}\n";
      select ($clientOut = $clientIn, undef, $cout = $clientIn, $timeout) or
         die "no answer from client: $opt{client}\n";

      $server->recv ($serverNtpMessage, length ($serverNtpMessage)) or
         die "error receving from $opt{server}: $!\n";
      $client->recv ($clientNtpMessage, length ($clientNtpMessage)) or
         die "error receving from $opt{client}: $!\n";

      processNtpData ($serverNtpMessage, $clientNtpMessage);
   }
   else {
      usage();
   }
}

if ($arg) {

   GetOptions (
      \%opt, 
      "server|s=s", 
      "client|c=s", 
      "timeout|t=i",
      "filename|f=s",
      "help|h") or usage ();
   usage () if $opt{help};

   if ($opt{timeout}) {

      $timeout = $opt{timeout};
   }

   if ($opt{server} && $opt{client}) {
      createSocket($opt{server}, $opt{client});
   }
   else { usage (); }
}
else { usage (); }

